'''
	This file is part of pyNN.
 
 	Copyright 2009 Michael Morckos <mikey.morckos@gmail.com>
 
 	pyNN is free software: you can redistribute it and/or modify
 	it under the terms of the GNU General Public License as published by
 	the Free Software Foundation, either version 3 of the License, or
 	(at your option) any later version.
 
 	pyNN is distributed in the hope that it will be useful,
 	but WITHOUT ANY WARRANTY; without even the implied warranty of
 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 	GNU General Public License for more details.
 
 	You should have received a copy of the GNU General Public License
 	along with pyNN.  If not, see <http://www.gnu.org/licenses/>.
'''

from neuron import *
from synapse import *
from layer import *
import data
import math

class NN :
		def __init__(self) :
				self.count = 0
				self.actual_outputs = []
				self.errors = []
				self.curr_error = 0.0
				self.epoch_errors = []
				self.input_node = Neuron()
				self.bias_node = Neuron(True)
				self.output_neuron = Neuron()
				self.layer = Layer(data.hidden_neurons)
				self.connect_synpases()
				
		def connect_synpases(self) :
				for n in self.layer.neurons :
						n.in_synapses.append(Synapse(self.input_node))
						n.in_synapses.append(Synapse(self.bias_node))
						self.output_neuron.in_synapses.append(Synapse(n))
		
		def execute(self) : 
				print "Alpha ", data.alpha, " Eita ", data.eita, " Hidden ", data.hidden_neurons, " Epochs ", data.epochs
				while self.count < data.epochs :
						self.actual_outputs.append([])
						self.errors.append([])
						self.single_run()
						self.compute_epoch_error()
						self.write_results()
						self.count += 1
				self.output_file.close()
				self.count = 0
		
		def single_run(self) :
				for i in range(0, len(data.inputs_vec)) :
						self.input_node.output_signal = data.inputs_vec[i]
						for n in self.layer.neurons :
								n.sum_func()
						self.output_neuron.sum_func()
						self.actual_outputs[self.count].append(self.output_neuron.output_signal)
						self.compute_error(i)
						if data.train :
								self.update_weights(i)
		
		def compute_error(self, i) :
				""" Calculating error at the output layer """
#				err = 0.5 * ((data.outputs_vec[i] - self.actual_outputs[self.count][i]) ** 2)
				err = (data.outputs_vec[i] - self.actual_outputs[self.count][i])
				self.errors[self.count].append(err)
				self.curr_error = err
		
		def compute_epoch_error(self) :
				res = 0.0
				for n in self.errors[self.count] :
						res += abs(n)
				self.epoch_errors.append((res * 0.5) / len(self.errors[self.count]))
				
		def update_weights(self, i) :
				""" Updating the weights for all neurons """
				doutput = data.lambda_coef * self.curr_error * (self.actual_outputs[self.count][i] * (1 - self.actual_outputs[self.count][i]))	
				for s in self.output_neuron.in_synapses :
						s.update_weight(doutput)
				for n in self.layer.neurons :
						doutput = data.lambda_coef * self.curr_error * (n.output_signal * (1 - n.output_signal))
						for s in n.in_synapses :
#								doutput = data.lambda_coef * s.weight * s.src_neuron.output_signal * (n.output_signal * (1 - n.output_signal))
								s.update_weight(doutput)
								
		def open_file(self, flag) :
				if flag :
						self.output_file = open("Training_Results", 'w')
				else :
						self.output_file = open("Testing_Results", 'w')						
		
		def write_results(self) :
				""" Printing Results """
				self.output_file.write("Epoch # " + str(self.count + 1) + "\n")
				self.output_file.write("Input".ljust(20) + 'Output'.ljust(20) + 'Network Output'.ljust(20) + "Error".ljust(20) + '\n')
				for i in range(0, len(data.inputs_vec)) : 		
						self.output_file.write(str(data.inputs_vec[i]).ljust(20) + str(data.outputs_vec[i]).ljust(20) + 
								str(self.actual_outputs[self.count][i]).ljust(20) + str(self.errors[self.count][i]).ljust(20) + '\n')
				self.output_file.write("Average Epoch Error : " + str(self.epoch_errors[self.count]))
				self.output_file.write("\n\n\n")								
				
